package com.jxpanda.starter.wechat;

import com.jxpanda.commons.toolkit.StringKit;
import com.jxpanda.commons.toolkit.json.JsonKit;
import com.jxpanda.starter.wechat.pojo.pay.WechatPayOrderData;
import com.jxpanda.starter.wechat.pojo.pay.WechatPayRefundData;
import com.jxpanda.starter.wechat.pojo.pay.WechatPayRequest;
import com.jxpanda.starter.wechat.pojo.pay.WechatPayResponse;
import com.jxpanda.starter.wechat.property.WechatProperties;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.function.Function;

/**
 * @author Panda
 */
@Slf4j
public class WechatPayService {

    @Resource
    private WechatProperties wechatProperties;

    @Resource
    private AesUtil wechatAesUtil;

    @Resource
    private CloseableHttpClient wechatPayHttpClient;

    @Resource
    private WechatUrlService wechatUrlService;

    /**
     * 统一下单接口
     */
    public Mono<WechatPayResponse.SignatureData> unifiedOrder(WechatPayRequest.UnifiedOrder unifiedOrder) {
        return Mono.just(unifiedOrder)
                .map(it -> createHttpPost(wechatUrlService.unifiedOrder(), it.fillProperties(
                        wechatProperties.getApplet().getAppId(),
                        wechatProperties.getMerchant().getMchId(),
                        wechatProperties.getMerchant().getNotifyUrl().getPay()))
                )
                .map(httpPost -> execute(httpPost, WechatPayResponse.UnifiedOrder.class).getPrepayId())
                .filter(StringKit::isNotBlank)
                .map(prepayId -> WechatPayResponse.SignatureData.signature(prepayId, wechatProperties.getApplet().getAppId(), wechatProperties.getMerchant().loadPrivateKey()))
                .defaultIfEmpty(WechatPayResponse.SignatureData.empty());

    }

    /**
     * 支付回调接口
     */
    public Mono<WechatPayResponse.Callback> payCallback(WechatPayRequest.Callback callbackParam, Function<WechatPayOrderData, Mono<Boolean>> callbackFunction) {
        return callbackHandler(callbackParam, WechatPayRequest.Callback.TRANSACTION_SUCCESS, WechatPayOrderData.class)
                .flatMap(callbackFunction)
                .map(success -> success ? WechatPayResponse.Callback.success() : WechatPayResponse.Callback.failed())
                .onErrorReturn(WechatPayResponse.Callback.failed())
                .defaultIfEmpty(WechatPayResponse.Callback.failed());
    }

    /**
     * 退款回调接口
     */
    public Mono<WechatPayResponse.Callback> refundCallback(WechatPayRequest.Callback callbackParam, Function<WechatPayRefundData, Mono<Boolean>> callbackFunction) {
        return callbackHandler(callbackParam, WechatPayRequest.Callback.REFUND_SUCCESS, WechatPayRefundData.class)
                .flatMap(callbackFunction)
                .map(success -> success ? WechatPayResponse.Callback.success() : WechatPayResponse.Callback.failed())
                .onErrorReturn(WechatPayResponse.Callback.failed())
                .defaultIfEmpty(WechatPayResponse.Callback.failed());
    }

    /**
     * 统一解密微信回调的密文
     */
    private <T> Mono<T> callbackHandler(WechatPayRequest.Callback callbackParam, String eventType, Class<T> dataClass) {
        return Mono.just(callbackParam)
                .filter(it -> eventType.equals(it.getEventType()))
                .map(WechatPayRequest.Callback::getResource)
                .mapNotNull(resource -> {
                    try {
                        return wechatAesUtil.decryptToString(resource.getAssociatedData().getBytes(StandardCharsets.UTF_8),
                                resource.getNonce().getBytes(StandardCharsets.UTF_8),
                                resource.getCiphertext());
                    } catch (GeneralSecurityException | IOException e) {
                        return Mono.error(e);
                    }
                })
                .cast(String.class)
                .map(dataJson -> JsonKit.fromJson(dataJson, dataClass));
    }


    public Mono<WechatPayOrderData> queryOrder(String outTradeNo) {
        return Mono.just(createHttpGet(wechatUrlService.queryOrder(outTradeNo)))
                .map(httpGet -> execute(httpGet, WechatPayOrderData.class));
    }

    /**
     * 关闭支付单
     */
    public Mono<Boolean> closeOrder(String outTradeNo) {
        // 关闭之前先查询，如果状态不对，就不关闭了
        return queryOrder(outTradeNo)
                // 支付成功，或者已经关闭的单子就不关闭了
                .filter(it -> !(it.getTradeState() == WechatPayOrderData.TradeState.SUCCESS || it.getTradeState() == WechatPayOrderData.TradeState.CLOSED))
                .flatMap(wechatPayOrderData -> Mono.just(createHttpGet(wechatUrlService.closeOrder(outTradeNo)))
                        .map(httpGet -> execute(httpGet, String.class))
                        // 这个函数没有消息体，所以没报错就是成功了
                        .map(it -> true))
                // 支付成功，或者已经关闭的单子就不关闭了,视为关闭成功
                .defaultIfEmpty(true);

    }

    /**
     * 微信退款接口
     */
    public Mono<WechatPayResponse.Refund> refund(WechatPayRequest.Refund refund) {
        return Mono.just(refund)
                .map(it -> {
                    if (StringKit.isBlank(it.getNotifyUrl())) {
                        it.setNotifyUrl(wechatProperties.getMerchant().getNotifyUrl().getRefund());
                    }
                    return createHttpPost(wechatUrlService.refunds(), refund);
                }).map(httpPost -> execute(httpPost, WechatPayResponse.Refund.class));
    }

    public Mono<WechatPayResponse.Refund> queryRefund(String outTradeNo) {
        return Mono.just(createHttpGet(wechatUrlService.queryRefunds(outTradeNo)))
                .map(httpGet -> execute(httpGet, WechatPayResponse.Refund.class));
    }


    @SneakyThrows
    private static HttpPost createHttpPost(String uri, Object param) {
        HttpPost httpPost = new HttpPost(uri);
        httpPost.addHeader("Content-Type", "application/json");
        httpPost.addHeader("Accept", "application/json");
        String request = JsonKit.toJson(param);
        httpPost.setEntity(new StringEntity(request, StandardCharsets.UTF_8));
        log.info("【微信支付接口调用】 ------ \nURL： + " + uri + "\n请求参数：" + request);
        return httpPost;
    }

    private static HttpGet createHttpGet(URI uri) {
        HttpGet httpGet = new HttpGet(uri);
        httpGet.addHeader("Accept", "application/json");
        return httpGet;
    }

    @SneakyThrows
    private <T> T execute(HttpUriRequest httpRequest, Class<T> clazz) {
        String resultJson = EntityUtils.toString(wechatPayHttpClient.execute(httpRequest).getEntity());
        log.info("【微信支付接口调用结束】 ------ 响应结果为：" + resultJson);
        return JsonKit.fromJson(resultJson, clazz);
    }

}
